package com.gitee.helioz.openai.common.util.http;

import com.gitee.helioz.openai.api.dto.OpenAiErrorResponse;
import com.gitee.helioz.openai.common.error.OpenAiRuntimeException;
import com.gitee.helioz.openai.common.util.json.GsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

/**
 * 〈http工具类〉
 *
 * @author <a href="https://gitee.com/helioz">Morning Starr</a>
 */
@Slf4j
public class HttpUtils {

    /**
     * 文件上传前缀
     */
    public static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentTypeEnum.MULTIPART.getType() + "; boundary=";

    /**
     * 发送GET请求
     *
     * @param url       请求地址
     * @param headerMap header数据
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendGetRequest(String url, Map<String, String> headerMap) throws IOException {
        log.debug("发送[GET]请求，URL=[{}]，设置请求头={}", url, headerMap);
        URL urlObj = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
        connection.setRequestMethod("GET");
        // 设置请求头
        if (headerMap != null && !headerMap.isEmpty()) {
            headerMap.forEach(connection::setRequestProperty);
        }
        int responseCode = connection.getResponseCode();
        log.debug("响应[GET]请求，URL=[{}]，responseCode={}", url, responseCode);
        if (responseCode == HttpURLConnection.HTTP_OK) {
            String responseBody = getResponseBody(connection);
            connection.disconnect();
            log.debug("响应[GET]请求，URL=[{}]，responseBody={}", url, responseBody);
            return responseBody;
        }
        handleError(connection, responseCode);
        return null;
    }

    /**
     * 发送POST请求
     *
     * @param url       请求地址
     * @param headerMap header数据
     * @param jsonBody  JSON请求体
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendPostJsonRequest(String url, Map<String, String> headerMap, String jsonBody) throws IOException {
        log.debug("发送[POST]请求，URL=[{}]，设置请求头={}, JSON BODY={}", url, headerMap, jsonBody);
        URL urlObj = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
        connection.setRequestMethod("POST");
        // 设置请求头
        if (headerMap != null && !headerMap.isEmpty()) {
            headerMap.forEach(connection::setRequestProperty);
        }
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setDoOutput(true);

        OutputStream outputStream = connection.getOutputStream();
        outputStream.write(jsonBody.getBytes());
        outputStream.flush();
        outputStream.close();

        int responseCode = connection.getResponseCode();
        log.debug("响应[POST]请求，URL=[{}]，responseCode={}", url, responseCode);
        if (responseCode == HttpURLConnection.HTTP_OK) {
            String responseBody = getResponseBody(connection);
            connection.disconnect();
            log.debug("响应[POST]请求，URL=[{}]，responseBody={}", url, responseBody);
            return responseBody;
        }
        handleError(connection, responseCode);
        return null;
    }

    /**
     * 发送表单提交请求
     *
     * @param url       请求地址
     * @param headerMap header数据
     * @param formMap   表单内容
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendPostFormData(String url, Map<String, String> headerMap, Map<String, Object> formMap) throws IOException {
        return sendPostFormData(url, headerMap, formMap, null);
    }

    /**
     * 发送表单提交请求
     *
     * @param url       请求地址
     * @param headerMap header数据
     * @param formMap   表单内容
     * @param fileParts 文件集合
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendPostFormData(String url, Map<String, String> headerMap, Map<String, Object> formMap, List<HttpFilePart> fileParts) throws IOException {
        int fileSize = fileParts == null ? 0 : fileParts.size();
        log.debug("发送[POST FORM]请求，URL=[{}]，设置请求头={}, form={}, fileSize={}", url, headerMap, formMap, fileSize);
        URL urlObj = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
        connection.setRequestMethod("POST");
        connection.setDoOutput(true);
        // 设置请求头
        if (headerMap != null && !headerMap.isEmpty()) {
            headerMap.forEach(connection::setRequestProperty);
        }
        OutputStream outputStream;
        // 方法内部flush
        if (fileSize > 0) {
            String boundary = "----MorningStarr_" + System.currentTimeMillis();
            connection.setRequestProperty("Content-Type", CONTENT_TYPE_MULTIPART_PREFIX + boundary);
            outputStream = connection.getOutputStream();
            writeMultipart(outputStream, formMap, fileParts, boundary);
        } else {
            connection.setRequestProperty("Content-Type", ContentTypeEnum.FORM_URLENCODED.getType());
            outputStream = connection.getOutputStream();
            writeFormUrlEncoded(outputStream, formMap);
        }
        outputStream.close();

        int responseCode = connection.getResponseCode();
        log.debug("响应[POST FORM]请求，URL=[{}]，responseCode={}", url, responseCode);
        if (responseCode == HttpURLConnection.HTTP_OK) {
            String responseBody = getResponseBody(connection);
            connection.disconnect();
            log.debug("响应[POST FORM]请求，URL=[{}]，responseBody={}", url, responseBody);
            return responseBody;
        }
        handleError(connection, responseCode);
        return null;
    }

    /**
     * 发送DELETE请求
     *
     * @param url       请求地址
     * @param headerMap header数据
     * @return 响应报文
     * @throws IOException IO异常
     */
    public static String sendDeleteRequest(String url, Map<String, String> headerMap) throws IOException {
        log.debug("发送[DELETE]请求，URL=[{}]，设置请求头={}", url, headerMap);
        URL urlObj = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
        connection.setRequestMethod("DELETE");
        // 设置请求头
        if (headerMap != null && !headerMap.isEmpty()) {
            headerMap.forEach(connection::setRequestProperty);
        }
        int responseCode = connection.getResponseCode();
        log.debug("响应[DELETE]请求，URL=[{}]，responseCode={}", url, responseCode);
        if (responseCode == HttpURLConnection.HTTP_OK) {
            String responseBody = getResponseBody(connection);
            connection.disconnect();
            log.debug("响应[DELETE]请求，URL=[{}]，responseBody={}", url, responseBody);
            return responseBody;
        }
        handleError(connection, responseCode);
        return null;
    }

    public static void writeFormUrlEncoded(OutputStream outputStream, Map<String, Object> formMap) throws IOException {
        if (formMap != null && !formMap.isEmpty()) {
            StringJoiner stringJoiner = new StringJoiner("&");
            formMap.forEach((k, v) -> {
                try {
                    stringJoiner.add(k).add(URLEncoder.encode(String.valueOf(v), StandardCharsets.UTF_8.name()));
                } catch (UnsupportedEncodingException e) {
                    throw new OpenAiRuntimeException(e);
                }
            });
            String requestBody = stringJoiner.toString();
            log.debug("发起[{}]请求，参数={}", ContentTypeEnum.FORM_URLENCODED.getType(), requestBody);
            outputStream.write(requestBody.getBytes());
            outputStream.flush();
        }
    }

    public static void writeMultipart(OutputStream outputStream, Map<String, Object> formMap, List<HttpFilePart> fileParts, String boundary) throws IOException {
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8), true);
        if (formMap != null && !formMap.isEmpty()) {
            formMap.forEach((k, v) -> {
                writer.println("--" + boundary);
                writer.println("Content-Disposition: form-data; name=\"" + k + "\"");
                writer.println();
                writer.println(v);
            });
        }
        // file upload
        if (fileParts != null && !fileParts.isEmpty()) {
            for (HttpFilePart filePart : fileParts) {
                writer.println("--" + boundary);
                writer.println("Content-Disposition: form-data; name=\"" + filePart.getPartName() + "\"; filename=\"" + filePart.getFileName() + "\"");
                writer.println("Content-Type: application/octet-stream");
                writer.println();
                outputStream.write(filePart.getFileBytes());
                writer.println();
            }
        }
        writer.println("--" + boundary + "--");
        writer.close();
        log.debug("发起[{}]请求", ContentTypeEnum.MULTIPART.getType());
    }

    public static String getResponseBody(HttpURLConnection connection) throws IOException {
        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        String inputLine;
        StringBuilder response = new StringBuilder();
        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();
        return response.toString();
    }

    public static void handleError(HttpURLConnection connection, int responseCode) throws IOException {
        // 响应错误，读取错误响应体
        BufferedReader errorIn = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
        String errorInputLine;
        StringBuffer errorResponse = new StringBuffer();
        while ((errorInputLine = errorIn.readLine()) != null) {
            errorResponse.append(errorInputLine);
        }
        errorIn.close();
        connection.disconnect();
        String responseBody = errorResponse.toString();
        log.debug("响应请求-Error，URL=[{}]，responseBody={}", connection.getURL().toString(), responseBody);
        if (StringUtils.isNotBlank(responseBody)) {
            OpenAiErrorResponse errorResponseDTO = GsonUtil.fromJson(responseBody, OpenAiErrorResponse.class);
            if (errorResponseDTO != null) {
                throw new IOException("execute request failed. Response Code: " + responseCode + ", Error: " + errorResponseDTO.getError().getMessage());
            }
            throw new IOException("execute request failed. Response Code: " + responseCode + ", Response Body: " + responseBody);
        }
        throw new IOException("execute request failed. Response Code: " + responseCode);
    }

}

